# -*- Python -*-

import os
import platform
import re
import subprocess
import sys

import lit.formats
import lit.util

from lit.llvm import llvm_config

sys.path.insert(0, os.path.dirname(__file__))
import eld_test_formats

# Configuration file for the 'lit' test runner.

def get_required_attr(config, attr_name):
  attr_value = getattr(config, attr_name, None)
  if attr_value == None:
    lit_config.fatal(
      "No attribute %r in test configuration! You may need to run "
      "tests from your build directory or add this attribute "
      "to lit.site.cfg " % attr_name)
  return attr_value

def which(cmdline):
  cmdline = cmdline.split(' ', 1)
  tool = cmdline[0]
  ret = lit.util.which(tool, config.environment['PATH'])
  if (ret is None):
    return "Not Found {}".format(tool)
  ret = ret.replace("\\", "/")
  return ' '.join([ret] + cmdline[1:])

# name: The name of this test suite.
config.name = 'eld'

# testFormat: The test format to use to interpret tests.
#
# For now we require '&&' between commands, until they get globally killed and
# the test runner updated.
execute_external = (platform.system() != 'Windows'
                    or lit_config.getBashPath() not in [None, ""])
config.test_format = eld_test_formats.get_test_format(config, execute_external)

# suffixes: A list of file extensions to treat as test files.
config.suffixes = ['.test', '.s', '.ll']

# excludes: A list of directories to exclude from the testsuite. The 'Inputs'
# subdirectories contain auxiliary inputs for various tests in their parent
# directories.
config.excludes = ['Inputs']

# test_source_root: The root path where tests are located.
config.test_source_root = os.path.dirname(__file__)

# test_exec_root: The root path where tests should be run.
eld_obj_root = getattr(config, 'eld_obj_root', None)
if eld_obj_root is not None:
    config.test_exec_root = os.path.join(eld_obj_root, 'test')

# Set llvm_{src,obj}_root for use by others.
config.llvm_src_root = getattr(config, 'llvm_src_root', None)
config.llvm_obj_root = getattr(config, 'llvm_obj_root', None)

# Tweak the PATH to include the tools dir and the scripts dir.
if eld_obj_root is not None:
    llvm_tools_dir = getattr(config, 'llvm_tools_dir', None)
    if not llvm_tools_dir:
        lit_config.fatal('No LLVM tools dir set!')
    path = os.path.pathsep.join((llvm_tools_dir, config.environment['PATH']))
    path = os.path.pathsep.join((os.path.join(getattr(config, 'llvm_src_root', None),'test','Scripts'),path))

    config.environment['PATH'] = path

    llvm_libs_dir = getattr(config, 'llvm_libs_dir', None)
    if not llvm_libs_dir:
        lit_config.fatal('No LLVM libs dir set!')

    # Propagate LLVM_SRC_ROOT into the environment.
    config.environment['LLVM_SRC_ROOT'] = getattr(config, 'llvm_src_root', '')

    # Propagate PYTHON_EXECUTABLE into the environment
    config.environment['PYTHON_EXECUTABLE'] = getattr(config, 'python_executable',
                                                      '')

eld_src_root = getattr(config, 'eld_src_root', None)
test_templates_dir = ""
if eld_src_root is not None:
    # YAML Map Parser
    path = os.path.join(eld_src_root, 'utils/YAMLMapParser')
    yamlmapparser = os.path.join(path, 'YAMLMapParser.py')
    yamlmapparser = yamlmapparser.replace("\\","/")
    test_templates_dir = os.path.join(eld_src_root, 'templates/')

# When running under valgrind, we mangle '-vg' onto the end of the triple so we
# can check it with XFAIL and XTARGET.
if lit_config.useValgrind:
    config.target_triple += '-vg'

# Shell execution
if platform.system() not in ['Windows'] or lit_config.getBashPath() != '':
    config.available_features.add('shell')

config.available_features.add('clangas')

# Add LTO
config.available_features.add('lto')

# Add yaml to available features
try:
    import yaml
    del yaml
    config.available_features.add('yaml')
except ImportError:
    pass

if ('RISCV64' in config.eld_targets_to_build or
     'AArch64' in config.eld_targets_to_build):
  config.available_features.add('64BitsArch')
else:
  config.available_features.add('32BitsArch')

# Use llvm-config to find what features are enabled
llvm_config.feature_config(
    [
        ("--assertion-mode", {"ON": "asserts"}),
    ]
)

# Default values
clang = 'clang'
clangxx = 'clang++'
driver = clang
driverxx = clangxx
clangxxopts = ''
embedclangopts = ''
embedclangxxopts = ''
clangas = 'clang'
clangasopts = '-cc1as'
opt = 'opt'
readelf = 'llvm-readelf'
objcopy = 'llvm-objcopy'
mcpu = ''
ar = 'llvm-ar'
nm = 'llvm-nm'
objdump = 'llvm-objdump'
addr2line = 'llvm-addr2line'
cnot = 'not'
# ARM/AArch64 can use ld.eld instead of arm-link.
# arm-link is slightly misleading on AArch64 unit tests
eld = 'ld.eld'
link = 'ld.eld'
elfcopy = 'llvm-objcopy'
strip = 'llvm-strip'
strings = 'llvm-strings'
llvmas = 'llvm-as'
llvmmc = 'llvm-mc'
obj2yaml = 'obj2yaml'
yaml2obj = 'yaml2obj'
dwarfdump = 'llvm-dwarfdump'
size = 'llvm-size'
linkg0opts = ''
hlink = ''
linkopts = ''
hlinkopts = ''
libdl = '-ldl'
clangopts = ''
clangg0opts = ''
linkdriveropts = ''
linkdriverg0opts = ''
# additional options to link a dynamic executable with the clang driver
linkdriverdynopts = "-Wl,-rpath,'$ORIGIN'"
tar = 'tar'
gnutaropts = ''
if platform.system() == 'Linux':
  gnutaropts = '--warning=no-timestamp'
mv = 'mv'
echo = 'echo'
diff = 'diff'
touch = 'touch'
libsdir = llvm_libs_dir
cmake = 'cmake'
git = 'git'
checkopts = '--dump-input-filter all'
cp = 'cp'
tail = 'tail'
xlen = 4
dirname = 'dirname'

# Control RISC-V Compression extension option, on by default
clangnorvcopts = ''
filecheck = 'FileCheck'
python = 'python'

if platform.system() == 'Windows':
    rm = which("rm") + " -rf"
    mkdir = which("mkdir") + " -p"
    grep = 'findstr'
    config.available_features.add('windows')
elif platform.system() == 'Linux':
    rm = 'rm -rf'
    mkdir = 'mkdir -p'
    grep = 'grep'
    config.available_features.add('linux')

if config.eld_option_name != "":
  config.available_features.add(config.eld_option_name)

# Add sanitizer specifics.
if config.has_sanitize == "ON":
  config.available_features.add('sanitize')

if config.has_tsan == "ON":
  config.available_features.add('tsan')

if config.eld_option_name == "thin_archives":
    config.available_features.add("thin_archives")
else:
    config.available_features.add("fat_archives")

# options common to all targets
common_opts = [
    "-fcommon", "-Wno-error=implicit-function-declaration",
    "-Wno-error=int-conversion",
    "-Wno-error=incompatible-function-pointer-types",
]
clangcommonopts = " ".join(common_opts) + " "

# Additional common options for c++.
clangcommonxxopts = " ".join([
    "-stdlib=libstdc++",
]) + " "

ar_opts = ""
if "thin_archives" in config.available_features:
    ar_opts = "--thin"

lsparserverifier = 'LSParserVerifier'
lsparserverifier_opts = ""


# `config.eld_targets_to_build` is actually the name of the test config.
config.test_target = config.eld_targets_to_build.split('_', 1)[0]

if config.test_target == 'Hexagon':
    clang = 'clang -target hexagon'
    config.arch = 'v75'
    config.emulation = '-m' + config.arch
    config.march = '-march=hexagon'
    config.mcpu = '-mcpu=hexagon' + config.arch
    embedclangopts = clangcommonopts + config.emulation
    embedclangxxopts = clangcommonopts + clangcommonxxopts + config.emulation
    clangopts = clangcommonopts + ' ' + config.emulation
    clangg0opts = clangcommonopts + '-G0 ' + config.emulation
    clangasopts = '-cc1as ' + config.emulation
    clangxx = 'clang++ -target hexagon'
    clangxxopts = clangcommonopts + clangcommonxxopts + ' -std=c++11'
    linkdriveropts = clangcommonopts + clangcommonxxopts + config.emulation
    linkdriverg0opts = linkdriveropts + ' -G0'
    link = 'ld.eld'
    linkopts = '--thread-count 4  --emit-relocs'
    if hasattr(config,'eld_option') and config.eld_option != 'default':
        linkopts = '--thread-count 4 ' + config.eld_option
    linkg0opts = '-G0'
    linkdriverdynopts = linkdriverdynopts + " -Wl,-Bdynamic,-E,--force-dynamic"
    ar = 'llvm-ar'
    nm = 'llvm-nm'
    objdump = 'llvm-objdump'
    size = 'llvm-size'
    if config.has_zlib == "1":
        config.available_features.add('zlib')
    #For Hexagon Linux.
    if 'hexagon-unknown-linux' in config.target_triple:
         clang ='clang'
         clangxx ='clang++'
         driver = clang
         driverxx = clangxx
         link = 'hexagon-linux-link'
         ar = 'llvm-ar'
         nm = 'llvm-nm'
         objdump = 'llvm-objdump'
         size = 'llvm-size'
    # Environment for hexagon-linux and standalone
    if 'hexagon-unknown-linux' in config.target_triple:
      config.available_features.add('hexagon-linux-env')
    else:
      config.available_features.add('hexagon-standalone-env')
    if ('hexagon-unknown-linux' in config.available_features or
        'hexagon-standalone-env' in config.available_features):
        config.available_features.add('hexagon')

if config.test_target == 'x86':
    config.march = ''
    clang = 'clang -target x86_64-linux-gnu'
    clangxx = 'clang++'
    llvmmc = 'llvm-mc'
    readelf = 'llvm-readelf'
    link = 'ld.eld'
    config.emulation = '-m elf_x86_64'
    ar = 'llvm-ar'
    nm = 'llvm-nm'
    objdump = 'llvm-objdump'
    dwarfdump = 'llvm-dwarfdump'

if config.test_target == 'ARM':
    config.march = '-march=arm'
    clang ='clang -target arm'
    clangxx ='clang++ -target arm'
    clangas = 'clang -target arm'
    # Features for buildbot to XFAIL tests while they are being fixed.
    if platform.system() == 'Windows':
        config.available_features.add('arm-windows')
        config.available_features.add('windows')
    elif platform.system() == 'Linux':
        config.available_features.add('arm-linux')
    if ('arm-windows' in config.available_features or
        'arm-linux' in config.available_features):
        config.available_features.add('arm')
    link = 'ld.eld'
    linkopts = '--thread-count 4  --threads'
    if hasattr(config,'eld_option') and config.eld_option != 'default':
        linkopts = '--thread-count 4 ' + config.eld_option
    linkg0opts = '--threads'
    clangopts = clangcommonopts + '-momit-leaf-frame-pointer'
    embedclangopts = clangcommonopts
    clangg0opts = clangcommonopts
    clangxxopts = clangcommonopts
    embedclangxxopts = clangcommonopts
    linkdriveropts = clangcommonopts + clangcommonxxopts
    linkdriverg0opts = linkdriveropts
    config.emulation = '-m armelf'
    clangasopts = ''
    if config.target_triple:
        if re.match(r'.*-android', config.target_triple):
            config.available_features.add("ndk-build")

if config.test_target == 'AArch64':
    config.march = '-march=aarch64'
    # Features for buildbot to XFAIL tests while they are being fixed.
    if platform.system() == 'Windows':
        config.available_features.add('aarch64-windows')
    elif platform.system() == 'Linux':
        config.available_features.add('aarch64-linux')
    if ('aarch64-windows' in config.available_features or
        'aarch64-linux' in config.available_features):
        config.available_features.add('aarch64')
    xlen = 8
    config.emulation = '-m aarch64elf'
    link = 'ld.eld -m aarch64elf -mtriple aarch64-unknown-none-elf'
    clang = 'clang -target aarch64-unknown-none-elf'
    clangxx = 'clang++ -target aarch64-unknown-none-elf'

    clangas = 'clang'
    linkopts = '--thread-count 4  --threads'
    if hasattr(config,'eld_option') and config.eld_option != 'default':
        linkopts = '--thread-count 4 ' + config.eld_option
    linkg0opts = '--threads'
    clangopts = clangcommonopts +'-fno-asynchronous-unwind-tables -mllvm -fast-isel=true'
    embedclangopts = clangcommonopts +'-fno-asynchronous-unwind-tables'
    clangg0opts = clangcommonopts +'-fno-asynchronous-unwind-tables -mllvm -fast-isel=true'
    clangxxopts = clangcommonopts +'-fno-asynchronous-unwind-tables -mllvm -fast-isel=true'
    embedclangxxopts = clangcommonopts +'-fno-asynchronous-unwind-tables'
    linkdriveropts = clangcommonopts + clangcommonxxopts
    linkdriverg0opts = linkdriveropts
    clangasopts = '-cc1as -triple aarch64'
    if config.target_triple:
        if re.match(r'.*-android', config.target_triple):
            config.available_features.add("ndk-build")

if config.test_target == 'RISCV64':
    config.march = '-march=riscv64'
    config.available_features.add('riscv64')
    xlen = 8
    clang = 'clang -target riscv64'
    clangxx = 'clang++ -target riscv64'
    link = 'ld.eld'
    config.emulation = '-m elf64lriscv'
    linkg0opts = '-G0'
    clangopts = clangcommonopts + ' -fno-asynchronous-unwind-tables'
    embedclangopts = clangcommonopts
    clangg0opts = clangcommonopts + ' -G0'
    clangnorvcopts = clangcommonopts + '-march=rv64imafd'
    clangxxopts = clangcommonopts + ' -fno-asynchronous-unwind-tables'
    embedclangxxopts = clangcommonopts
    linkdriveropts = clangcommonopts + clangcommonxxopts
    linkdriverg0opts = linkdriveropts + ' -G0'
    linkopts = '--thread-count 4  --emit-relocs'
    if hasattr(config,'eld_option') and config.eld_option != 'default':
        linkopts = '--thread-count 4 ' + config.eld_option
    llvmmc = 'llvm-mc'
    readelf = 'llvm-readelf'
    ar = 'llvm-ar'
    nm = 'llvm-nm'
    objdump = 'llvm-objdump'
    dwarfdump = 'llvm-dwarfdump'

if config.test_target == 'RISCV':
    config.march = '-march=riscv32'
    config.available_features.add('riscv32')
    clang = 'clang -target riscv32'
    clangxx = 'clang++ -target riscv32'
    link = 'ld.eld'
    config.emulation = '-m elf32lriscv'
    linkg0opts = '-G0'
    clangopts = clangcommonopts + ' -fno-asynchronous-unwind-tables'
    embedclangopts = clangcommonopts
    clangg0opts = clangcommonopts + ' -G0'
    clangnorvcopts = clangcommonopts + '-march=rv32imafd'
    clangxxopts = clangcommonopts + ' -fno-asynchronous-unwind-tables'
    embedclangxxopts = clangcommonopts
    linkdriveropts = clangcommonopts + clangcommonxxopts
    linkdriverg0opts = linkdriveropts + ' -G0'
    linkopts = '--thread-count 4 -mtriple riscv32-unknown-elf  --emit-relocs'
    if hasattr(config,'eld_option') and config.eld_option != 'default':
        linkopts = '-mtriple riscv32-unknown-elf --thread-count 4 ' + config.eld_option
    llvmmc = 'llvm-mc'
    readelf = 'llvm-readelf'
    ar = 'llvm-ar'
    nm = 'llvm-nm'
    objdump = 'llvm-objdump'
    dwarfdump = 'llvm-dwarfdump'

cnot = which(cnot)
clangxx = which(clangxx)
driver = which(driver)
driverxx = which(driverxx)
llvmmc = which(llvmmc)
llvmas = which(llvmas)
clangas = which(clangas)
clang = which(clang)
elfcopy = which(elfcopy)
strip = which(strip)
eld = which(eld)
link = which(link)
if (hlink != ''):
  hlink = which(hlink)
opt = which(opt)
readelf = which(readelf)
addr2line = which(addr2line)
ar = which(ar)
nm = which(nm)
objdump = which(objdump)
dwarfdump = which(dwarfdump)
strings = which(strings)
size = which(size)
obj2yaml = which(obj2yaml)
yaml2obj = which(yaml2obj)
filecheck = which(filecheck) + ' ' + checkopts
if platform.system() == 'Windows':
    python = which(python)
elif platform.system() == 'Linux':
    python = which("python3")

tar = which(tar) + ' --force-local'
mv = which(mv)
diff = which(diff)
touch = which(touch)
cmake = config.cmake_executable
git = which(git)
lsparserverifier = which(lsparserverifier)
dirname = which(dirname)

# Detect musl-clang
muslclang = which('musl-clang')
if 'Not Found' not in muslclang:
    config.available_features.add('musl')
    muslclang = ' '.join(['env LD=eld', muslclang])
else:
    muslclang = None

# Set target-wise path where tests should be run.
config.test_exec_root = os.path.join(config.test_exec_root, config.eld_targets_to_build)

lit_config.note('----------------------------------------------------------')
lit_config.note('Configuring for Target : {}'.format(config.eld_targets_to_build))
lit_config.note('----------------------------------------------------------')
lit_config.note('using cnot: {}'.format(which(cnot)))
lit_config.note('using driver: {}'.format(driver))
lit_config.note('using driverxx: {}'.format(driverxx))
lit_config.note('using clangxx: {}'.format(which(clangxx)))
lit_config.note('using llvmmc: {}'.format(which(llvmmc)))
lit_config.note('using llvmas: {}'.format(which(llvmas)))
lit_config.note('using clangas: {}'.format(which(clangas)))
lit_config.note('using clang: {}'.format(which(clang)))
lit_config.note('using elfcopy: {}'.format(which(elfcopy)))
lit_config.note('using strip: {}'.format(which(strip)))
lit_config.note('using eld: {}'.format(eld))
lit_config.note('using link: {}'.format(link))
lit_config.note('using link-option: {}'.format(linkopts))
lit_config.note('using link-option-name: {}'.format(config.eld_option_name))
lit_config.note('using hlink: {}'.format(which(hlink)))
lit_config.note('using opt: {}'.format(which(opt)))
lit_config.note('using readelf: {}'.format(which(readelf)))
lit_config.note('using addr2line: {}'.format(which(addr2line)))
lit_config.note('using ar: {}'.format(which(ar)))
lit_config.note('using nm: {}'.format(which(nm)))
lit_config.note('using objdump: {}'.format(which(objdump)))
lit_config.note('using dwarfdump: {}'.format(which(dwarfdump)))
lit_config.note('using strings: {}'.format(which(strings)))
lit_config.note('using size: {}'.format(which(size)))
lit_config.note('using obj2yaml: {}'.format(which(obj2yaml)))
lit_config.note('using yaml2obj: {}'.format(which(yaml2obj)))
lit_config.note('using filecheck: {}'.format(which(filecheck)))
lit_config.note('using filecheck options: {}'.format(checkopts))
lit_config.note('using python: {}'.format(which(python)))
lit_config.note('using grep: {}'.format(which(grep)))
lit_config.note('using rm: {}'.format(which("rm")))
lit_config.note('using mkdir: {}'.format(which("mkdir")))
lit_config.note('using YAMLMapParser: {}'.format(yamlmapparser))
lit_config.note('using Tar: {}'.format(tar))
lit_config.note('using mv: {}'.format(mv))
lit_config.note('using echo: {}'.format(echo))
lit_config.note('using diff: {}'.format(diff))
lit_config.note('using touch: {}'.format(touch))
lit_config.note('using cmake: {}'.format(cmake))
lit_config.note('using git: {}'.format(git))
lit_config.note('using dirname: {}'.format(dirname))
lit_config.note('using test templates directory: {}'.format(test_templates_dir))
if muslclang is not None:
    lit_config.note('using musl-clang: {}'.format(muslclang))
else:
    lit_config.note('musl-clang not found - tests with REQUIRES: musl will be skipped')

# Add substitutions.
config.substitutions.append( ("%xlen", str(xlen)))
config.substitutions.append( ("%aropts", ar_opts))
config.substitutions.append( ("%clangxxopts","".join(clangxxopts)) )
config.substitutions.append( ("%embedclangxxopts","".join(embedclangxxopts)) )
config.substitutions.append( ("%clangasopts","".join(clangasopts)) )
config.substitutions.append( ("%clangg0opts","".join(clangg0opts)) )
config.substitutions.append( ("%clangopts","".join(clangopts)) )
config.substitutions.append( ("%clangnorvcopts","".join(clangnorvcopts)) )
config.substitutions.append( ("%embedclangopts","".join(embedclangopts)) )
config.substitutions.append( ("%linkg0opts","".join(linkg0opts)) )
config.substitutions.append( ("%linkopts","".join(linkopts)) )
config.substitutions.append( ("%hlinkopts","".join(hlinkopts)) )
config.substitutions.append( ("%linkdriverdynopts","".join(linkdriverdynopts)) )
config.substitutions.append( ("%libdl","".join(libdl)) )
config.substitutions.append( ("%libsdir","".join(libsdir)) )
config.substitutions.append( ("%eldsrcroot","".join(eld_src_root)) )
config.substitutions.append( ("%llvmobjroot","".join(config.llvm_obj_root)) )
config.substitutions.append( ("%linkdriveropts","".join(linkdriveropts)) )
config.substitutions.append( ("%linkdriverg0opts","".join(linkdriverg0opts)) )
config.substitutions.append( ("%lsparserverifier_opts", lsparserverifier_opts))

#subsitute binaries.
config.substitutions.append( ("%not","".join(cnot)) )
config.substitutions.append( ("%clangxx","".join(clangxx)) )
config.substitutions.append( ("%llvm-mc","".join(llvmmc)) )
config.substitutions.append( ("%llvm-as","".join(llvmas)) )
config.substitutions.append( ("%clangas","".join(clangas)) )
config.substitutions.append( ("%clang","".join(clang)) )
config.substitutions.append( ("%elfcopy","".join(elfcopy)) )
config.substitutions.append( ("%strip","".join(strip)) )
config.substitutions.append( ("%eld","".join(eld)) )
config.substitutions.append( ("%link","".join(link)) )
config.substitutions.append( ("%driverxx", driverxx) )
config.substitutions.append( ("%driver", driver) )
config.substitutions.append( ("%hlink","".join(hlink)) )
config.substitutions.append( ("%opt","".join(opt)) )
config.substitutions.append( ("%readelf","".join(readelf)) )
config.substitutions.append( ("%objcopy","".join(objcopy)) )
config.substitutions.append( ("%addr2line","".join(addr2line)) )
config.substitutions.append( ("%ar","".join(ar)) )
config.substitutions.append( ("%nm","".join(nm)) )
config.substitutions.append( ("%objdump","".join(objdump)) )
config.substitutions.append( ("%dwarfdump","".join(dwarfdump)) )
config.substitutions.append( ("%strings","".join(strings)) )
config.substitutions.append( ("%size","".join(size)) )
config.substitutions.append( ("%obj2yaml","".join(obj2yaml)) )
config.substitutions.append( ("%yaml2obj","".join(yaml2obj)) )
config.substitutions.append( ("%filecheck","".join(filecheck)) )
config.substitutions.append( ("%python","".join(python)) )
config.substitutions.append( ("%yamlmapparser","".join(yamlmapparser)) )
config.substitutions.append( ("%rm","".join(rm)) )
config.substitutions.append( ("%mkdir","".join(mkdir)) )
config.substitutions.append( ("%grep","".join(grep)) )
config.substitutions.append( ("%gnutaropts","".join(gnutaropts)) )
config.substitutions.append( ("%tar","".join(tar)) )
config.substitutions.append( ("%mv","".join(mv)) )
config.substitutions.append( ("%echo","".join(echo)) )
config.substitutions.append( ("%diff","".join(diff)) )
config.substitutions.append( ("%touch","".join(touch)) )
config.substitutions.append( ("%cppcompiler","".join(config.cpp_compiler)) )
config.substitutions.append( ("%cmake", cmake) )
config.substitutions.append( ("%git", git))
config.substitutions.append( ("%cp", cp))
config.substitutions.append( ("%tail", tail))
config.substitutions.append( ("%test_templates_dir", test_templates_dir))
config.substitutions.append( ("%lsparserverifier", lsparserverifier) )
config.substitutions.append( ("%mcpu","".join(mcpu)) )
config.substitutions.append( ("%dirname","".join(dirname)) )
config.substitutions.append( ("%emulation","".join(config.emulation)) )
if muslclang is not None:
    config.substitutions.append( ("%musl-clang","".join(muslclang)) )
